How to list all subcollections of a Cloud Firestore document?

Renaud Tarnec
Firebase Tips & Tricks
6 min readOct 24, 2019

--

As detailed in the Cloud Firestore documentation, data in Firestore is stored into documents, which are “organized into collections”. Documents can contain subcollections which, in turn, can contains documents.

The documentation also indicates that:

Documents in subcollections can contain subcollections as well, allowing you to further nest data. You can nest data up to 100 levels deep.

Normally, as a Firestore database architect, while working out your data model, you decide the ids of the different subcollections of a document.

For example, for a Restaurants Reviews application, you may decide to have a reviews subcollection for each restaurant document¹.

And, with the JavaScript SDK, you would query all the review documents for a specific restaurant document with an id of 123 as follows:

db.collection("restaurants").doc("123").collection("reviews").get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.id, " => ", doc.data());
});
});

However, in some specific cases, it may happen that subcollections are created dynamically, by the users, while using the application.

For example, let’s imagine a CRM application where users create Customer Visit reports. Each user of the application has a user’s document in the Firestore database. For each customer visit, the corresponding visit report is saved in a subcollection of the user’s document which has the id of the customer².

The figure below illustrates this data model, showing the user1 document with two subcollections corresponding to two customers: CUST_1234 and CUST_6541.

user1 document has two customers subcollections (CUST_1234 and CUST_6541)

Each of these two collections was created the first time user1 created a document within the collection (i.e. created a visit report for the corresponding customer).

Now, imagine that, in the application, you need to list these collections, for example to allow the user selecting the client for which he/she wants to list the visit reports.

The question is: How do you get this list of subcollections, from the application?

If you look a the Firestore documentation, you will see that:

Retrieving a list of collections is not possible with the mobile/web client libraries.

Fortunately, there are several possible workarounds to get a list of (sub)collections from a web/mobile app. Let’s detail a couple of them!

Approach #1: Save the list in a dedicated field of the parent document

As the title says, this approach consists in having a field in the subcollections’ parent document that holds the subcollections ids. When you want to get the list, you simply fetch the parent document and read the value of this field. The best is to use a field of type Array.

How to populate this field?
Simply use arrayUnion(), as explained in the documentation, here and here, and as shown in the example below (JavaScript SDK):

Example of updating the subcollection ids Array field in the parent document, using a batched write

Note that, since we don’t know upfront if the subcollection id has already been added to the array field, we need to update this field each time we write a new document in the subcollection, multiplying the write cost by two³.

An alternative would be to read the parent document before writing to the collection, to check if the subcollection id is already present in the array. But, in addition to generating an extra “roundtrip” to the database, it would incur the cost of one document read.

Another drawback of this approach, is the fact that you need to manage the case when all the documents of a subcollection are deleted. This is more complex than it seems: you either need to maintain a documents counter for each subcollection or, worst, need to query the entire subcollection to count the documents (using the get() method and the size property) …

In other words, this first approach, which at first glance seems promising, implies quite a lot of extra reads and writes, which in turn generate some additional cost…

Fortunately (again!), there is another possible approach.

Approach #2: Use a Cloud Function

While it is not possible, with the mobile and web client libraries, to retrieve the list of subcollections of a document, it is possible with the Cloud Firestore server client libraries.

Therefore, it is possible to use the Cloud Firestore Node.js Client API to write a Cloud Function that lists the subcollections of a document.

Since we will call this Cloud Function from the app we use a Callable Cloud Function⁴.

Here is the code of the Cloud Function:

As you can see it is quite simple. Let’s detail each line of this code⁵.

  1. We import the Cloud Functions and Admin SDK modules using Node require statements.
  2. We initialize an admin app instance.
  3. We use functions.https.onCall() to create the Callable Cloud Function. This method takes two parameters: data and context (optional).
  4. We use the dataparameter to get docPath, the value of the Firestore document path (slash-separated). This value is passed from the client calling the Cloud Function (see below).
  5. We then call the asynchronous listCollections() method on the DocumentReference created by using docPath (i.e. admin.firestore().doc(docPath)). The listCollections() method returns a Promise that resolves with an array of CollectionReferences. Note that listCollections() fetches only the subcollections that are direct children of the document.
  6. We then use the map() method to create a new array with the ids of the subcollections. The id property of a CollectionReferences holds the last path element of the referenced collection.
  7. Finally, we send back to the client a JavaScript object that can be JSON encoded and that contains the array of subcollection ids.

Calling this Cloud Function from the client is even easier. Here is the code for the JavaScript SDK, in order to call it from a web application:

Calling the getSubCollections Callable Cloud Function with the JavaScript SDK

Here is how it works:

  1. We declare an HttpsCallable, which is “a reference to a callable http trigger in Cloud Functions”. When called, it returns a Promise that resolves with an HttpsCallableResult, that, in turns, wraps a single result.
  2. We then call it, passing the Firestore document path (collectionId/documentId) in an object.
  3. We finally use the data property of the HttpsCallableResult to get the collections array returned by the getSubCollection Cloud Function.

We can then do whatever we want with this array: writing it to the console, or looping over it and printing each collection id, or even looping over it and fetching, for each collection (i.e. for each element of the array), all the documents of the collection, etc.

Note that it works similarly with the Android and iOS SDKs (see the documentation) as well with FlutterFire (see the documentation).

That’s it! We now have a way to get all the subcollections of a given Firestore document, from a client (Web, Android or iOS). And the subcollections array is immediately adapted if a subcollection is added or deleted.

In addition, this solution does not imply any extra document read or write.

It only costs one Cloud Function call, which is not expensive ($0.40/million of invocations). Moreover Firebase offers a generous free tier of “2,000,000 invocations, 400,000 GB-sec, 200,000 CPU-sec, and 5 GB of Internet egress traffic” each month. For more details on the exact costs, see here and here.

You will find in the following github repository the code of a small Firebase project which includes:

  • The Cloud Function code, as presented above;
  • A simple HTML page that demonstrates how it works from a web client.

Deploy it to one of your Firebase project (see the readme file) and open the root url of the project (https://<your-project-id>.firebaseapp.com) with your preferred browser.

Just enter a document path in the dedicated field and click the button “Get Subcollections”:

  • If the document has one or more subcollections the page will display their id(s).
  • If the document at the path does not exist or if it does not have any subcollection, the Cloud Function will return an empty array.

If you have any question or suggestion, please leave a comment below.

#BetterTogether

[1] Actually this is one of the examples used in this official Firestore video https://youtu.be/v_hR4K4auoQ

[2] Of course, this is just one of the possibilities for modeling this specific case. Other approaches may be totally valid! The idea is just to use this data model as an example of subcollections with ids assigned dynamically by the users.

[3] I.e. one write for the subcollection document and one write for the update of the parent document.

[4] We could have chosen an HTTPS Cloud Function, but Callable Functions have several advantages compare to HTTPS ones, as explained in the doc.

[5] Note that some parts of the explanation text that follows are directly copied/pasted from the Firebase documentation!

--

--

Renaud Tarnec
Firebase Tips & Tricks

Google Developer Expert for Firebase / Full-Stack web application Dev & Architect